<template>
<div class="right-all full-height relative-position">
<!--  头部-->
  <div class="file-head row no-wrap bg-primary text-white form-inline">
    <q-btn label="上传文件" unelevated color="white" text-color="primary" class="margin-r-10" >
      <input ref="uploadInput" type="file" @change="uploadFile($event)" multiple
             class="q-uploader__input overflow-hidden absolute-full" />
    </q-btn>
    <q-btn label="下载文件" :disable="actFile.length===0"
           unelevated color="white" text-color="primary" class="margin-r-10">
      <input ref="uploadInput" webkitdirectory multiple
            @click="testSele($event)"
             v-show="actFile.length>0"
             class="q-uploader__input overflow-hidden absolute-full opacity-0" />
    </q-btn>
    <q-btn @click="showCompare" label="文件对比" unelevated color="white" text-color="primary" />
    <q-space></q-space>
    <q-icon name="iconfont icontubiao-58" class="icon" @click="back()">
      <q-tooltip>返回上一级</q-tooltip>
    </q-icon>
    <q-icon :name="lookStatus==='list'?'iconfont icontubiao-104':'iconfont icontubiao-57'" class="icon" @click="changeLookStatus">
      <q-tooltip>切换显示模式</q-tooltip>
    </q-icon>
    <q-icon name="iconfont icontubiao-34" class="icon" @click="refreshFun">
      <q-tooltip>刷新</q-tooltip>
    </q-icon>
  </div>
<!--  路径-->
  <position :position="curDir.name" :total="total" @goto="goto"></position>
<!--  内容-->
  <div class="list-content drop-target relative-position"
       @mousedown="boxStart($event,'fileList')" ref="fileList"
  >
    <div class="full-height" v-if="lookStatus==='bigIcon'" @click.self="clearActFile()">
      <q-scroll-area :thumb-style="CommonConfig.thumbStyle" class="full-height">
        <div class="full-height list">
          <file-item v-for="(file, fileIndex) in files" :key="fileIndex" :ref="'fileItem'+file.id"
                     :file="file" :file-index="fileIndex" :is-active="isActDay(file)"
                     @selFiles="selFiles" @dblclickE="dblclickE" @setFile="setFile"
                     @setNewNameFun="setNewNameFun" @deleteFun="deleteFun"
                     @addCompareList="addCompareList" @setDragItem="setDragItem"
                     @editFile="editFile" @showVersionList="showVersionList"
          />
        </div>
      </q-scroll-area>
      <q-menu touch-position context-menu>
        <q-list dense style="min-width: 150px">
          <q-item clickable v-close-popup @click="newFolder">
            <q-item-section>新建文件夹</q-item-section>
          </q-item>
          <q-item clickable v-close-popup @click="newFile">
            <q-item-section>新建文件</q-item-section>
          </q-item>
        </q-list>
      </q-menu>
    </div>
    <div class="full-height" v-else @click.self="clearActFile">
      <q-table class="table-file" :pagination="pagination" no-data-label="暂无数据"
               :data="files" :columns="columns" row-key="id" hide-bottom
               binary-state-sort @request="onRequest">
        <template v-slot:body="props">
          <file-item-tr :file="props" :is-active="isActDay(props.row)" :ref="'fileItemTr'+props.row.id"
                        @selFiles="selFiles" @dblclickE="dblclickE" @setFile="setFile"
                        @setNewNameFun="setNewNameFun" @deleteFun="deleteFun"
                        @addCompareList="addCompareList" @setDragItem="setDragItem"
                        @editFile="editFile" @showVersionList="showVersionList"
          />
        </template>
      </q-table>
      <q-menu touch-position context-menu>
        <q-list dense style="min-width: 150px">
          <q-item clickable v-close-popup @click="newFolder">
            <q-item-section>新建文件夹</q-item-section>
          </q-item>
          <q-item clickable v-close-popup @click="newFile">
            <q-item-section>新建文件</q-item-section>
          </q-item>
        </q-list>
      </q-menu>
    </div>
    <div class="box-select" ref="boxSelect"></div>
    <q-inner-loading :showing="loading">
      <q-spinner-gears size="50px" color="primary" />
    </q-inner-loading>
  </div>
  <!--        底部-->
  <div class="file-bottom">
    <div class="progressResult text-primary text-underline cursor-pointer"
         @click="showProgressResult">
      传输记录：正在传输({{progressFiles.length}})； 传输成功({{successProgressFiles.length}})； 传输失败({{failProgressFiles.length}})</div>
  </div>
<!--  文件内容编辑-->
  <code-edit ref="codeEdit" :value="editFileContent" :name="editFileName" :decode="edtiFileDecode"
             :path="editFilePath" :state="codeState" @refreshList="refreshFun" @save="saveFile" />
  <!--  文件对比列表-->
  <compare-file ref="CompareFile"
                :client="client" :requestId="requestId"
                :drag-item="dragItem" :stat-pos="statPos" @compare="compareDetail" />
  <!--  版本履历-->
  <version-list ref="versionList"></version-list>
  <!--  右下角正在上传的弹框-->
  <progress-dialog ref="progressDialog" :list="progressFiles" />
  <!--  文件传输记录-->
  <progress-result-dialog ref="progressResult" :doing-data="doingProgressFiles"
                          :success-data="successProgressFiles" :fail-data="failProgressFiles"
                          @delOneRecord="delOneRecord" @clearRecord="clearRecord" />
  <!--  文件内容对比查看-->
  <code-merge ref="codeMerge"></code-merge>
</div>
</template>

<script>
import Position from './position'
import FileItem from './file-item'
import FileItemTr from './file-item-tr'
import CodeEdit from 'src/components/code-edit/index'
import CompareFile from './compare-file1'
import VersionList from './version-list'
import ProgressDialog from './progress-dialog'
import ProgressResultDialog from './progress-result-dialog'
import CodeMerge from 'src/components/code-merge'
import { getOffsetLeft, getOffsetTop, getFiletype, matchFileType } from 'src/assets/js/util'
import { loadProgressSuccessRecord, loadProgressFailRecord,
  saveProgressSuccessRecord, saveProgressFailRecord,
  clearProgressSuccessRecord, clearProgressFailRecord, loadOss, saveOss } from 'src/assets/js/storage'
import { newDir, newFile, editFile, setNewName,
  rmDir, readFile, addProgressRecord, getOSSAccount,
  cloudFileExist, readFile as readFileCloud } from 'src/boot/program/cloud'
import { getCloudList, rmFileToRecyclebin, getChildPath } from 'src/boot/program/G-gov'
import { fileExist, readFile as readFileLocal, newDir as newLocalDir, downloadFile } from 'src/assets/js/file-dir'
import { cncToBox, fileMachineLocal } from 'src/boot/program/machine'
import boxErr from 'src/assets/json/boxErr'
import boxCodeErr from 'src/assets/json/boxCodeErr'
import { mapGetters } from 'vuex'
import { date } from 'quasar'
import fileMqttMixin from 'src/assets/js/file-mqtt-mixin'
let OSS = require('ali-oss')
const path = require('path')

export default {
  name: 'right-all',
  mixins: [fileMqttMixin],
  data () {
    return {
      lookStatus: 'bigIcon', // 查看状态, list or bigIcon
      // 路径
      position: '全部文件', // 当前位置
      // 当前选中的文件夹
      curDir: {
        size: null,
        label: '',
        name: '',
        path: '',
        source: 'cloud'
      },
      total: 0, // 一共加载的数量
      // 列表位置
      loading: false, // 是否是加载状态
      actFile: [], // 选中的文件
      clickTime: null, // 区分单击与双击
      files: [],
      emptyMenu: false, // 控制空白处右键显示的菜单
      // 编辑内容
      editFileContent: '', // 要编辑文件的内容
      editFileName: '', // 要编辑文件的名字
      edtiFileDecode: '', // 要编辑文件的编码
      editFilePath: '', // 要编辑文件的路径
      codeState: 'edit', // 添加编辑状态，edit or add
      // 表格配置
      columns: [ // 列表的头部
        { name: 'label', align: 'left', label: '文件名', field: 'label', style: 'width: 60%' },
        { name: 'size', align: 'left', label: '大小', field: 'size' },
        { name: 'timestamp', label: '修改时间', field: 'timestamp', sortable: true }
      ],
      pagination: {
        sortBy: 'timestamp',
        descending: true,
        page: 1,
        rowsPerPage: 0,
        rowsNumber: 10
      },
      // 框选数据
      parentW: 0, // 外框的宽
      parentH: 0, // 外框的高
      parentX: 0, // 外框的横坐标
      parentY: 0, // 外框的中坐标
      statPos: 'localCloud', // 定义埋点位置
      dragItem: [], // 存储拖动的数据
      // 传输数据
      progressFiles: [], // 正在传输的文件
      doingProgressFiles: [], // 传输成功的列表
      successProgressFiles: [], // 传输成功的列表
      failProgressFiles: [], // 传输失败的列表
      progressTime: null, // 存储定时器
      clientCloud: null, // oss上传下载用
      // 文件对比
      compareList: [] // 文件对比数据
    }
  },
  computed: {
    ...mapGetters(['userInfo'])
  },
  created () {
    this.successProgressFiles = this.sortRecord(loadProgressSuccessRecord(this.userInfo.tenant_id))
    this.failProgressFiles = this.sortRecord(loadProgressFailRecord(this.userInfo.tenant_id))
    this.getInfo()
  },
  methods: {
    // 切换显示状态
    changeLookStatus () {
      this.lookStatus = this.lookStatus === 'list' ? 'bigIcon' : 'list'
      this.pagination.sortBy = 'timestamp'
      this.pagination.descending = true
      this.refreshFun()
    },
    // 刷新
    refreshFun () {
      this.getInfo(this.curDir.path)
    },
    // 返回上一级
    back () {
      let pathArr = this.curDir.path.split('/')
      pathArr.pop()
      pathArr.pop()
      let path
      if (pathArr.length > 1) {
        path = pathArr.join('/') + '/'
      } else {
        path = ''
      }
      this.getInfo(path)
    },
    goto (pathIndex) {
      let pathArr = this.curDir.path.split('/')
      pathArr = pathArr.slice(0, pathIndex + 1)
      let path = pathIndex === 0 ? '' : pathArr.join('/') + '/'
      this.getInfo(path)
    },
    /**
     * 获取数据
     * @param path 路径
     */
    getInfo (path = '') {
      this.loading = true
      getCloudList(path).then((res) => {
        let dirs = this.initInfo(res.data.dirs, false)
        let files = this.initInfo(res.data.files, true, res.data.dirs.length)
        this.files = this.lookStatus === 'bigIcon' || (this.lookStatus === 'list' && this.pagination.sortBy && this.pagination.descending)
          ? [].concat(dirs, files) : [].concat(files, dirs)
        this.total = this.files.length
        this.curDir = {
          ...res.data.curDir,
          source: 'cloud'
        }
      }).finally(() => {
        this.loading = false
      })
    },
    /**
     * 给数据添加必要的属性
     * @param info
     * @param isFile 是否是文件
     * @returns 新数据
     */
    initInfo (info, isFile, dirLength = 0) {
      let data = info.map((item, index) => {
        let type = isFile ? getFiletype(item.label) : 'other'
        let id = isFile ? index + 1 + dirLength : index + 1
        return {
          ...item,
          id,
          _name: item.label,
          isFile: isFile,
          type,
          source: 'cloud',
          icon: isFile ? 'img:statics/svg/' + type + '.svg' : 'img:statics/svg/file-icon.svg',
          children: []
        }
      })
      if (this.lookStatus === 'list' && this.pagination.sortBy) {
        let sortBy = this.pagination.sortBy
        let descending = this.pagination.descending
        const sortFn = (descending
          ? (a, b) => (a[sortBy] > b[sortBy] ? -1 : a[sortBy] < b[sortBy] ? 1 : 0)
          : (a, b) => (a[sortBy] > b[sortBy] ? 1 : a[sortBy] < b[sortBy] ? -1 : 0)
        )
        data = data.sort(sortFn)
      }
      return data
    },
    // 排序
    onRequest (props) {
      const { sortBy, descending } = props.pagination
      if (sortBy) {
        const sortFn = (descending
          ? (a, b) => (a[sortBy] > b[sortBy] ? -1 : a[sortBy] < b[sortBy] ? 1 : 0)
          : (a, b) => (a[sortBy] > b[sortBy] ? 1 : a[sortBy] < b[sortBy] ? -1 : 0)
        )
        let dirs = this.files.filter(item => !item.isFile).sort(sortFn)
        let files = this.files.filter(item => item.isFile).sort(sortFn)
        this.files = descending ? [].concat(dirs, files) : [].concat(files, dirs)
        this.pagination.sortBy = sortBy
        this.pagination.descending = descending
      }
    },
    // 单击选中文件，按住shift、ctrl多选
    selFiles (e, item) {
      clearTimeout(this.clickTime)
      this.clickTime = setTimeout(() => {
        if (e.ctrlKey) {
          // 按住ctrl键
          const index = this.actFile.findIndex(file => {
            return file.id === item.id
          })
          if (index > -1) {
            this.actFile = this.actFile.filter(file => {
              return file.id !== item.id
            })
          } else {
            this.actFile.push(item)
          }
        } else if (e.shiftKey) {
          // 按住shift键
          if (this.actFile.length === 0) {
            this.actFile.push(item)
          } else {
            const maxIndex = this.getMaxIndex()
            const index = this.files.findIndex(file => {
              return file.id === item.id
            })
            let actFile = this.files.filter((file, fileIndex) => {
              const condition = (index > maxIndex && fileIndex <= index && fileIndex >= maxIndex) ||
                (index < maxIndex && fileIndex >= index && fileIndex <= maxIndex) ||
                (index === maxIndex && fileIndex === index)
              return condition
            })
            this.actFile = actFile
          }
        } else {
          // 单选
          const index = this.actFile.findIndex(file => {
            return file.id === item.id
          })
          this.actFile = index > -1 ? [] : [item]
        }
      }, 250)
    },
    // 获取存储在选中的file里面的最大最小角标
    getMaxIndex () {
      const indexArr = []
      this.actFile.forEach(item => {
        const index = this.files.findIndex(file => {
          return file.id === item.id
        })
        if (index > -1) {
          indexArr.push(index)
        }
      })
      indexArr.sort((a, b) => a > b ? 1 : -1)
      return indexArr[indexArr.length - 1]
    },
    // 选中一个文件
    setFile (item) {
      this.actFile = [item]
    },
    // 双击进入子级文件夹
    dblclickE (file) {
      clearTimeout(this.clickTime)
      if (!file.isFile) {
        this.actFile = []
        this.curDir = file
        this.getInfo(file.path)
      }
    },
    // 清空选中文件列表
    clearActFile (event) {
      // if (event.target.className === '')
      this.actFile = []
    },
    // 是否是选中状态
    isActDay (file) {
      const index = this.actFile.findIndex(item => {
        return item.id === file.id
      })
      return index > -1
    },
    // 重命名文件夹、文件
    setNewNameFun (item, rename) {
      let path = item.isFile ? item.path : item.path.slice(0, item.path.length - 1)
      const params = {
        newKey: rename,
        oldKey: path
      }
      let ref = (this.lookStatus === 'bigIcon' ? 'fileItem' : 'fileItemTr') + item.id
      setNewName(params).then(res => {
        if (this.$refs[ref].edit) {
          this.$refs[ref].edit = false
        } else {
          this.$refs[ref][0].edit = false
        }
        this.refreshFun()
      }).catch(() => {
        if (this.$refs[ref].rename) {
          this.$refs[ref].rename = item.label
        } else {
          this.$refs[ref][0].rename = item.label
        }
      })
    },
    // 删除文件和文件夹
    deleteFun (item) {
      this.$q.dialog({
        title: `<p class="dialog-notice-title">
<i class="iconfont icontubiao-28" ></i> 确定要删除${item.label}吗？</p>`,
        html: true,
        ok: {
          unelevated: true,
          label: '确认'
        },
        cancel: {
          unelevated: true,
          outline: true,
          label: '取消'
        }
      }).onOk(data => {
        if (item.isFile) {
          let params = {
            key: item.path,
            size: item.size
          }
          rmFileToRecyclebin(params).then(res => {
            this.refreshFun()
          })
        } else {
          let path = item.path
          rmDir(path).then(res => {
            this.refreshFun()
          })
        }
      })
    },
    // 新建文件夹
    newFolder () {
      this.$q.dialog({
        class: 'prompt-dialog',
        title: '文件夹名称',
        prompt: {
          model: '',
          isValid: val => val.length > 0 && val.length < 20,
          type: 'text'
        },
        ok: {
          unelevated: true,
          label: '确认'
        },
        cancel: {
          unelevated: true,
          outline: true,
          label: '取消'
        },
        persistent: true
      }).onOk(data => {
        if (data.length === 0) {
          this.noticeMessage('名称不能为空')
          return false
        }
        if (data.length > 20) {
          this.noticeMessage('名称长度必须小于20')
          return false
        }
        let findIndex = this.files.findIndex(item => !item.isFile && item.label === data)
        if (findIndex > -1) {
          this.noticeMessage(`文件夹${data}已存在`)
          return false
        }
        let path = this.curDir.path
        newDir(path + data + '/').then(res => {
          this.refreshFun()
        })
      })
    },
    // 新建文件
    newFile () {
      this.$q.dialog({
        class: 'prompt-dialog',
        title: '文件名称',
        prompt: {
          model: '',
          type: 'text'
        },
        ok: {
          unelevated: true,
          label: '确认'
        },
        cancel: {
          unelevated: true,
          outline: true,
          label: '取消'
        },
        persistent: true
      }).onOk(data => {
        if (data.length === 0) {
          this.noticeMessage('名称不能为空')
          return false
        }
        if (data.length > 20) {
          this.noticeMessage('名称长度必须小于20')
          return false
        }
        let findIndex = this.files.findIndex(item => item.isFile && item.label === data)
        if (findIndex > -1) {
          this.noticeMessage(`文件${data}已存在`)
          return false
        }
        this.editFilePath = this.curDir.path + data
        this.editFileName = data
        this.editFileContent = ''
        this.edtiFileDecode = ''
        this.codeState = 'add'
        this.$refs.codeEdit.showDialog()
      })
    },
    // 编辑读取文件
    editFile (item) {
      if (matchFileType(item.label)) {
        this.errMessage('无法编辑图片文件、word文件、视频文件、音频文件、快捷方式、压缩文件！')
        return
      }
      if (!this.testFileSize(item)) return
      this.editFileName = item.label
      readFile(item.path).then(res => {
        this.editFileContent = res.data
        this.editFilePath = item.path
        this.codeState = 'edit'
        this.$refs.codeEdit.showDialog()
      })
    },
    // 保存文件
    saveFile (fileName, code) {
      let frontPath = this.curDir.path
      let nameArr = fileName.split('/')
      if (this.codeState === 'add') {
        const params = {
          dir: frontPath,
          fileName: nameArr[nameArr.length - 1],
          content: code
        }
        newFile(params).then(res => {
          this.successMessage(`添加文件${this.editFileName}成功`)
          this.refreshFun()
          this.$refs.codeEdit.hideDialog()
        })
      } else if (this.codeState === 'edit') {
        const params = {
          key: fileName,
          content: code
        }
        editFile(params).then(res => {
          this.successMessage(`修改文件${this.editFileName}成功`)
          this.refreshFun()
          this.$refs.codeEdit.hideDialog()
        })
      }
    },
    // 验证文件大小是否超过5M
    testFileSize (file) {
      if (file.size > 5 * 1024 * 1024) {
        this.errMessage('文件超过5M暂不支持查看！')
        return false
      }
      return true
    },
    // 显示版本履历
    showVersionList (file) {
      this.$refs.versionList.showDialog(file)
    },

    // 下面是文件对比相关
    // 加入对比列表
    addCompareList (file) {
      this.$refs.CompareFile.show(file)
      this.actFile = []
    },
    // 显示文件对比
    showCompare () {
      this.$refs.CompareFile.show()
      this.actFile = []
    },
    // 存储拖动数据
    setDragItem (item) {
      this.actFile = [item]
      this.dragItem = [item]
    },
    // 读取文件内容，弹出比较弹框
    compareDetail (compareList) {
      if (!this.allowSize(compareList)) {
        return false
      }
      let machineArr = compareList.filter(item => item.source === 'machine')
      let machineFileNum = machineArr.length
      this.compareList = compareList
      this.$q.loading.show()
      // 没有机床文件
      if (machineFileNum === 0) {
        const result = this.readFile(compareList)
        Promise.all(result).then(res => {
          let content1 = {
            title: compareList[0].label,
            content: res[0].data
          }
          let content2 = {
            title: compareList[1].label,
            content: res[1].data
          }
          this.$refs.codeMerge.showDialog(content1, content2)
          this.$refs.CompareFile.hide()
        }).finally(() => {
          this.$q.loading.hide()
        })
      } else if (machineFileNum === 1) {
        // 先读取云端或本地文件
        const result = this.readFile(compareList)
        let notMachineFile = compareList.find(item => item.source !== 'machine')
        Promise.all(result).then(res => {
          let content1 = {
            title: notMachineFile.label,
            content: res[0].data
          }
          // 再读取机床文件
          this.readMachineFile(machineArr[0], content1)
        })
      } else if (machineFileNum === 2) {
        // 两个都为机床文件的对比
        let machineItem = compareList[0]
        this.readMachineFile(machineItem)
      }
    },
    // 比较文件的大小是否符合规则
    allowSize (compareList) {
      for (let i = 0; i < compareList.length; i++) {
        if (matchFileType(compareList[i].label)) {
          this.errMessage('无法读取图片文件、word文件、视频文件、音频文件、快捷方式、压缩文件！')
          return false
        }
        if (compareList[i].source === 'local' && compareList[i].size > 5 * 1024 * 1024) {
          this.errMessage('无法读取超过5M的本地文件')
          return false
        }
        if (compareList[i].source === 'cloud' && compareList[i].size > 5 * 1024 * 1024) {
          this.errMessage('无法读取超过5M的云端文件')
          return false
        }
        if (compareList[i].source === 'machine' && compareList[i].size > 1024 * 1024) {
          this.errMessage('无法读取超过1M的机床文件')
          return false
        }
      }
      return true
    },
    // 读取本地和云端对比文件的内容
    readFile (compareList) {
      let result = []
      compareList.forEach(item => {
        // 读取云端内容
        if (item.source === 'cloud') {
          result.push(readFileCloud(item.path))
        }
        // 读取本地文件内容
        if (item.source === 'local') {
          let getFileLocal = new Promise((resolve, reject) => {
            readFileLocal(item.path,
              () => {
                this.errMessage('文件读取失败')
                reject('文件读取失败')
              },
              (str, decode) => {
                resolve({ data: str })
              })
          })
          result.push(getFileLocal)
        }
      })
      return result
    },
    // 读取设备端文件
    readMachineFile (item, prevContent) {
      if (item.position === 'cnc') {
        this.putMachineInside(item, prevContent)
      } else {
        this.putMachine(item, item.path, prevContent)
      }
    },
    // 开始发送内部存储文件到外部存储文件
    putMachineInside (send, prevContent) {
      let requestId1 = 'webClient' + this.userInfo.user_id.toString() + Date.parse(new Date()).toString()
      // let self = this
      let params1 = {
        boxId: send.boxId,
        cncPath: send.path,
        requestId: requestId1
      }
      this.$emit('onSubscribe', send.boxId)
      cncToBox(params1)
      this.client.onMessageArrived = (message) => {
        console.log(message)
        let data = JSON.parse(message.payloadString)
        try {
          // 这里是放置对其他消息进行处理
          if (requestId1 === data.request_id && data.hasOwnProperty('error')) {
            if (data.error === 0) {
              this.putMachine(send, data.file_path, prevContent)
            } else {
              this.$q.loading.hide()
              this.errMessage(`文件内容读取失败！${boxErr[data.error]}`)
            }
          }
        } catch (e) {
          console.error(e)
          this.errMessage('文件内容读取失败！')
        }
      }
    },
    // 从机床传输到云端的方法
    putMachine (send, path, prevContent) {
      // let self = this
      let params = {
        boxId: send.boxId,
        fileName: send.label,
        filePath: path,
        fromBox: send.position === 'box' ? 1 : 0
      }
      let requestId
      fileMachineLocal(params).then(res => {
        requestId = res.data.requestId
        // cloudDownloadUrl = res.data.url
        this.onSubscribe2(res.data.requestId)
      })
      this.client.onMessageArrived = (message) => {
        let data = JSON.parse(message.payloadString)
        try {
          // 这里是放置对其他消息进行处理
          if (requestId === data.request_id) {
            if (data.hasOwnProperty('source') && data.source === 'send_result') {
              if (data.code === 200) {
                // 盒子上传到云端已完成，接下来是从云端获取内容
                this.getFileContent(send, data.url, prevContent)
              } else {
                this.$q.loading.hide()
                this.errMessage(`文件${send.label}读取失败！${boxCodeErr[data.code]}`)
              }
            }
          }
        } catch (e) {
          console.error(e)
          this.errMessage('文件内容读取失败')
        }
      }
    },
    // 通过云端接口读取云端文件内容
    getFileContent (send, sendPath, prevContent) {
      readFileCloud(sendPath).then(res => {
        let machineContent = {
          title: send.label,
          content: res.data
        }
        if (prevContent) {
          this.$refs.codeMerge.showDialog(prevContent, machineContent)
          this.$refs.CompareFile.hide()
          this.$q.loading.hide()
        } else {
          let machineItem = this.compareList[1]
          this.readMachineFile(machineItem, machineContent)
        }
      }).catch(() => {
        this.$q.loading.hide()
      })
    },

    // 传输文件-上传文件
    // 本地上传文件到云端
    async uploadFile (event) {
      const files = event.target.files
      // 判断是否选择了文件
      if (files.length === 0) {
        return
      }
      let oss = await this.getOss()
      this.clientCloud = new OSS({
        region: oss.region,
        accessKeyId: oss.accessKeyId,
        accessKeySecret: oss.accessKeySecret,
        stsToken: oss.securityToken,
        bucket: oss.bucket
      })

      // 验证文件是否符合规则
      if (!this.testCloudFiles(files)) {
        return false
      }
      for (let i = 0; i < files.length; i++) {
        let file = files[i]
        // 判断文件是否存在
        let savePath = (this.curDir.path === '' ? this.userInfo.tenant_id + '/' : this.curDir.path) + file.name
        cloudFileExist(savePath).then(res => {
          if (res.data) {
            this.$q.dialog({
              title: `<p class="dialog-notice-title">
<i class="iconfont icontubiao-28" ></i> 提示</p>`,
              message: '文件' + file.name + '已存在，是否覆盖文件?',
              html: true,
              ok: {
                unelevated: true,
                label: '确认'
              },
              cancel: {
                unelevated: true,
                outline: true,
                label: '取消'
              },
              persistent: true
            }).onOk(data => {
              // 发送文件
              this.putToCloud(file, savePath)
            })
          } else {
            // 发送文件
            this.putToCloud(file, savePath)
          }
        })
      }
      event.target.value = ''
    },
    /**
     * 发送本地文件到云端的方法并监听进度
     * @param sendFile 要发送的文件
     * @param savePath 要发送到云端的位置
     * @returns {Promise<void>}
     */
    putToCloud (sendFile, savePath) {
      let self = this
      let retryCount = 0 // 上传计数
      let retryCountMax = 3 // 最多上传3次
      let checkpoint
      let startTime = new Date() // 上传开始时间
      let acceptPath = (this.curDir.name === '' ? '全部文件/' : this.curDir.name) + sendFile.name
      if (sendFile.size === 0) {
        let progress = {
          name: sendFile.name, // 文件名字
          progress: 1, // 传输进度
          state: 'fail', // 上传状态 upload:正在上传，download:正在下载，success：传输成功，fail：传输失败
          partSize: 100 * 1024, // 每次上传的大小
          fileSize: sendFile.size, // 文件总大小
          acceptPath, // 接收方地址
          startTime, // 开始时间
          sender: 'local', // 发送方
          acceptor: 'cloud', // 接收方
          reason: '文件内容不允许为空',
          endTime: date.formatDate(new Date(), 'MM-DD HH:mm:ss'),
          useTime: self.getUseTime(startTime, new Date())
        }
        this.addProgressFiles(progress)
        return
      }
      // 记录上传进度函数
      let progress = (percentage, cpt) => {
        checkpoint = cpt
        if (cpt) {
          let progress = {
            name: sendFile.name, // 文件名字
            progress: percentage, // 传输进度
            state: 'upload', // 上传状态 upload:正在上传，download:正在下载，success：传输成功，fail：传输失败
            partSize: cpt.partSize.toFixed(2), // 每次上传的大小
            fileSize: sendFile.size, // 文件总大小
            acceptPath, // 接收方地址
            startTime, // 开始时间
            sender: 'local', // 发送方
            acceptor: 'cloud', // 接收方
            endTime: date.formatDate(new Date(), 'MM-DD HH:mm:ss'),
            useTime: self.getUseTime(startTime, new Date())
          }
          this.addProgressFiles(progress)
        }
      }
      // 上传函数
      const uploadFile = function uploadFile () {
        let options = {
          checkpoint,
          partSize: 100 * 1024, // 分段上传的大小
          timeout: 120000, // 超时时间
          progress
        }
        return self.clientCloud.multipartUpload(savePath, sendFile, options).then(res => {
          let progress = {
            name: sendFile.name, // 文件名字
            progress: 1, // 传输进度
            state: 'success', // 上传状态 upload:正在上传，download:正在下载，success：传输成功，fail：传输失败
            partSize: 100 * 1024, // 每次上传的大小
            fileSize: sendFile.size, // 文件总大小
            acceptPath, // 接收方地址
            startTime, // 开始时间
            sender: 'local', // 发送方
            acceptor: 'cloud', // 接收方
            endTime: date.formatDate(new Date(), 'MM-DD HH:mm:ss'),
            useTime: self.getUseTime(startTime, new Date())
          }
          self.addProgressFiles(progress)
          self.refreshFun()
        }).catch(err => {
          if (self.clientCloud && self.clientCloud.isCancel()) {
            let progress = {
              name: sendFile.name, // 文件名字
              progress: 1, // 传输进度
              state: 'fail', // 上传状态 upload:正在上传，download:正在下载，success：传输成功，fail：传输失败
              partSize: 100 * 1024, // 每次上传的大小
              fileSize: sendFile.size, // 文件总大小
              acceptPath, // 接收方地址
              startTime, // 开始时间
              sender: 'local', // 发送方
              acceptor: 'cloud', // 接收方
              reason: '',
              endTime: date.formatDate(new Date(), 'MM-DD HH:mm:ss'),
              useTime: self.getUseTime(startTime, new Date())
            }
            self.addProgressFiles(progress)
            self.refreshFun()
          } else {
            console.error(err)
            // 上传出错，继续上传
            if (retryCount < retryCountMax) {
              retryCount++
              console.log('再次上传，retryCount : ' + retryCount)
              uploadFile()
            }
          }
        })
      }
      uploadFile()
    },
    // 获取oss账号
    async getOss () {
      let oss = loadOss()
      if (!oss && !oss.expiration) {
        await this.refreshOSSAccout().then(newOss => {
          oss = newOss
        })
      } else {
        let overTime = new Date(oss.expiration)
        let now = new Date()
        if (now.getTime() < overTime.getTime() - 60) {
          return oss
        } else {
          await this.refreshOSSAccout().then(newOss => {
            oss = newOss
          })
        }
      }
      return oss
    },
    // 更新oss账号
    refreshOSSAccout () {
      return getOSSAccount().then(res => {
        let oss = res.data
        saveOss(oss)
        return Promise.resolve(oss)
      })
    },
    // 验证上传到云端的文件是否符合规则
    testCloudFiles (files) {
      if (files.length > 50) {
        this.errMessage('不能超过50个文件')
        return false
      }
      for (let i = 0; i < files.length; i++) {
        if (files[i].size > 50 * 1024 * 1024) {
          this.errMessage(`文件${files[i].name}大小超过50M，请重新选择要传输的文件`)
          return false
        }
      }
      return true
    },

    // 传输文件-下载文件
    // 验证是否选择了文件
    testSele (event) {
      if (this.actFile.length === 0) {
        // 阻止冒泡
        if (event.stopPropagation) {
          event.stopPropagation()
        } else {
          event.cancelBubble = true
        }
        if (event.preventDefault) {
          event.preventDefault()
        } else {
          event.returnValue = false
        }
        this.noticeMessage('请先选择要下载的文件')
        return false
      }
      let dialog
      dialog.showOpenDialog({
        // 选择操作，此处是打开文件夹
        properties: [
          'openDirectory'
        ]
      }).then(result => {
        if (!result.canceled) {
          this.downloadFiles(result.filePaths[0])
        }
      }).catch(err => {
        console.log(err)
      })
    },
    // 云端下载文件到本地
    async downloadFiles (event) {
      let localPath = event
      localPath = path.normalize(localPath) + path.sep

      let oss = await this.getOss()
      this.clientCloud = new OSS({
        region: oss.region,
        accessKeyId: oss.accessKeyId,
        accessKeySecret: oss.accessKeySecret,
        stsToken: oss.securityToken,
        bucket: oss.bucket
      })
      // 当前选中的文件
      let files = this.actFile.filter(item => {
        return item.isFile
      })
      // 当前选中的文件夹
      let dirs = this.actFile.filter(item => {
        return !item.isFile
      })
      let { resultDirs, resultFiles } = await this.getChildByDirs(files, dirs)
      if (!this.testCloudFiles(resultFiles)) {
        return false
      }
      // 创建文件夹和文件
      this.createDir(resultDirs, localPath)
      this.downloadFile(resultFiles, localPath)
    },
    // 获取云端子文件下的所有文件和文件夹
    async getChildByDirs (files, dirs) {
      let resultChild = []
      // let clientCloud = new OSS({
      //   region: 'oss-cn-shanghai',
      //   accessKeyId: 'LTAI4FeqMts5wCPDWc1Bzo7y',
      //   accessKeySecret: '7lpZiUNW2oT1UQfF0znmCd6qunOJxF',
      //   bucket: 'yuzhen-dlp-test'
      // })
      for (let i = 0; i < dirs.length; i++) {
        let item = dirs[i]
        // result返回(末尾以/结尾的是文件夹)
        // objects:[
        //   {name: '1/aa/'},
        //   {name: '1/aa/123.txt'},
        //   {name: '1/aa/2/'},
        // ]
        let result = await getChildPath(item.path).then(res => {
          return res.data
        })
        // let result = await clientCloud.list({
        //   prefix: item.path,
        //   delimiter: ''
        // })
        resultChild = resultChild.concat(result)
      }
      let resultDirs = [], resultFiles = files
      resultChild.forEach(item => {
        if (item.name[item.name.length - 1] === '/') {
          resultDirs.push({
            ...item,
            isFile: false
          })
        } else {
          let labels = item.name.split('/')
          resultFiles.push({
            ...item,
            label: labels[labels.length - 1],
            isFile: true
          })
        }
      })
      return { resultDirs, resultFiles }
    },
    // 下载云端创建文件夹
    createDir (dirs, localPath) {
      // 当前云端所在路径
      // let curPath = this.curDir.name.length > 0 ? this.curDir.name : this.userInfo.user_nickname + '的文件夹/'
      dirs.forEach(dir => {
        // 去掉当前位置的路径
        let name = dir.path.replace(this.curDir.path, '')
        // 本地要创建文件夹的路径（文件夹路径以斜杠结尾）
        let localDirPath = path.normalize(localPath + name)
        console.log(localDirPath)
        // 获取文件夹名称
        // let dirname = dirs.label
        // 如果文件夹不存在，创建文件夹
        if (!fileExist(localDirPath)) {
          newLocalDir(localDirPath, (err) => {
            if (err) {
              console.warn(err)
              this.errMessage(`创建文件夹${name}失败`)
            }
          })
        }
      })
    },
    // 下载云端文件
    downloadFile (files, localPath) {
      // 当前云端所在路径
      // let curPath = this.curDir.name.length > 0 ? this.curDir.name : this.userInfo.user_nickname + '的文件夹/'
      // let siblingNodePath = this.siblingNode.path.length > 0 ? this.siblingNode.path : this.userInfo.tenant_id + '/'
      // 遍历每个文件，获取blob对象，下载文件
      files.forEach(file => {
        // 去掉当前位置的路径
        // let name = file.path.replace(curPath, '')
        let name = file.path.replace(this.curDir.path, '')
        console.log(name)
        // 本地要创建文件夹的路径
        let localFilePath = path.normalize(localPath + name)
        console.log(localFilePath)
        // 获取文件夹名称
        let filename = path.parse(localFilePath).base
        // 判断文件是否存在
        if (fileExist(localFilePath)) {
          this.$q.dialog({
            title: `<p class="dialog-notice-title">
<i class="iconfont icontubiao-28" ></i> 提示</p>`,
            message: '文件' + filename + '已存在，是否覆盖文件',
            html: true,
            ok: {
              unelevated: true,
              label: '确认'
            },
            cancel: {
              unelevated: true,
              outline: true,
              label: '取消'
            },
            persistent: true
          }).onOk(data => {
            this.putToLocal(file, localFilePath)
          })
        } else {
          this.putToLocal(file, localFilePath)
        }
      })
    },
    /**
     * 发送本地到云端文件的方法并监听进度
     * @param sendFile 要发送的云端文件文件
     * @param localFilePath 要发送到本地的位置
     * @returns {Promise<void>}
     */
    async putToLocal (sendFile, localFilePath) {
      console.log(localFilePath)
      // 判断要上传的文件里的文件夹是否存在
      if (!this.dirExist(localFilePath)) return
      let startTime = new Date()
      let progress = {
        name: sendFile.label, // 文件名字
        progress: 0, // 传输进度
        state: 'download', // 上传状态 upload:正在上传，download:正在下载，success：传输成功，fail：传输失败
        partSize: sendFile.size, // 每次上传的大小
        fileSize: sendFile.size, // 文件总大小
        acceptPath: localFilePath, // 接收方地址
        startTime, // 开始时间
        sender: 'cloud', // 发送方
        acceptor: 'local', // 接收方
        endTime: date.formatDate(new Date(), 'MM-DD HH:mm:ss'),
        useTime: this.getUseTime(startTime, new Date())
      }
      this.addProgressFiles(progress)
      let url = await this.clientCloud.signatureUrl(sendFile.path)
      // 流式下载文件
      await downloadFile(url, localFilePath, (info) => {
        let fileRate = info.progress
        progress.size = info.size
        progress.progress = fileRate
        progress.receiveBytes = info.receiveBytes
        progress.state = info.state
        progress.endTime = date.formatDate(new Date(), 'MM-DD HH:mm:ss')
        progress.useTime = this.getUseTime(startTime, new Date())
        this.addProgressFiles(progress)
        if (info.state === 'success') {
          this.refreshFun()
        }
      })
    },
    /**
     * 判断要上传的文件里的文件夹是否存在
     * @param localFilePath 要上传文件的路径
     * @returns {boolean}
     */
    dirExist (localFilePath) {
      let pathArr = localFilePath.split('/')
      let newPathArr = pathArr.slice(0, pathArr.length - 1)
      let dirPath = newPathArr.join('/') + '/'
      return fileExist(dirPath)
    },

    // 传输过程中相关
    // 添加上传中和上传完成的文件
    addProgressFiles (file) {
      // 添加传输目录
      const index = this.progressFiles.findIndex(item => {
        return item.name === file.name
      })

      // 开始传输
      if (index === -1) {
        this.progressFiles.unshift(file)
        this.$refs.progressDialog.showDialog()
      } else {
        this.progressFiles[index].progress = file.progress
        this.progressFiles[index].useTime = file.useTime
        this.progressFiles[index].state = file.state
      }
      // 传输失败
      if (file.state === 'fail') {
        this.failProgressFiles.unshift(file)
        saveProgressFailRecord(this.userInfo.tenant_id, this.failProgressFiles)
      } else if (file.state === 'success') {
        this.successProgressFiles.unshift(file)
        saveProgressSuccessRecord(this.userInfo.tenant_id, this.successProgressFiles)
        if (file.acceptor === 'cloud') {
          let params = {
            fileName: file.acceptPath,
            size: file.fileSize
          }
          addProgressRecord(params)
        }
      }
      this.doingProgressFiles = this.progressFiles.filter(item => {
        return item.state !== 'fail' && item.state !== 'success'
      })
      this.isOverProgress()
    },
    // 判断所有文件是否上传完成，完成择1.5s后消失
    isOverProgress () {
      if (this.doingProgressFiles.length === 0) {
        this.progressTime = setTimeout(() => {
          this.$refs.progressDialog.hideDialog()
          this.progressFiles = []
        }, 1500)
      } else {
        clearTimeout(this.progressTime)
      }
    },
    // 传输记录相关
    // 显示传输记录弹框
    showProgressResult () {
      this.$refs.progressResult.showDialog()
    },
    // 删除一条传输记录
    delOneRecord (state, file) {
      if (state === 'success') {
        this.successProgressFiles = this.successProgressFiles.filter(item => {
          return !(item.name === file.name && item.startTime === file.startTime)
        })
        saveProgressSuccessRecord(this.userInfo.tenant_id, this.successProgressFiles)
      }
      if (state === 'fail') {
        this.failProgressFiles = this.failProgressFiles.filter(item => {
          return item.name !== file.name && item.startTime !== file.startTime
        })
        saveProgressFailRecord(this.userInfo.tenant_id, this.failProgressFiles)
      }
    },
    // 清空传输记录
    clearRecord (state) {
      if (state === 'success') {
        this.successProgressFiles = []
        clearProgressSuccessRecord(this.userInfo.tenant_id, this.successProgressFiles)
      }
      if (state === 'fail') {
        this.failProgressFiles = []
        clearProgressFailRecord(this.userInfo.tenant_id, this.failProgressFiles)
      }
    },
    // 传输记录按照时间排序
    sortRecord (record) {
      let newRecord = record.sort((a, b) => {
        if (a.endTime < b.endTime) {
          return 1
        } else if (a.endTime > b.endTime) {
          return -1
        } else return 0
      })
      newRecord = newRecord.map(item => {
        return {
          ...item,
          acceptPath: item.hasOwnProperty('acceptPath') ? item.acceptPath.replace('//', '/') : '/'
        }
      })
      return newRecord
    },

    // 框选
    // 鼠标按下事件
    boxStart (event, parentDom) {
      if (event.target.tagName === 'IMG' ||
        event.target.tagName === 'SPAN' ||
        event.target.className.indexOf('txt-ellipsis') > -1) {
        return true
      }
      // 判断鼠标是否是左键按下
      if (event.buttons !== 1 || event.which !== 1) return
      let parentNode = this.$refs[parentDom]
      this.parentX = getOffsetLeft(parentNode)
      this.parentY = getOffsetTop(parentNode)
      this.parentW = parentNode.clientWidth
      this.parentH = parentNode.clientHeight
      let self = this
      clearTimeout(this.mouseTime)
      let startTime = new Date()

      this.mouseOn = true
      this.startX = event.clientX - this.parentX
      this.startY = event.clientY - this.parentY
      let boxSelect = this.$refs.boxSelect
      boxSelect.style.display = 'block'
      boxSelect.style.left = this.startX + 'px'
      boxSelect.style.top = this.startY + 'px'
      boxSelect.style.zIndex = -1
      boxSelect.style.width = 0
      boxSelect.style.height = 0
      document.onmousemove = function (event) {
        self.boxMove(event, parentDom)
      }
      this.$refs[parentDom].onmouseup = function (event) {
        // self.$refs[parentDom].onmousemove = null
        document.onmousemove = null
        clearTimeout(this.mouseTime)
        let endTime = new Date()
        if (endTime.getTime() - startTime.getTime() < 200) {
          self.isClickEvent = true
        }
        self.boxStop(event)
      }
    },
    // 鼠标移动事件
    boxMove (event, parentDom) {
      // 如果框选没有开启。退出
      if (!this.mouseOn) return

      let clientX = event.clientX
      let clientY = event.clientY
      let _x = clientX - this.parentX
      let _y = clientY - this.parentY
      let boxSelect = this.$refs.boxSelect

      boxSelect.style.left = Math.min(_x, this.startX) + 'px'
      boxSelect.style.top = Math.min(_y, this.startY) + 'px'
      boxSelect.style.width = Math.abs(_x - this.startX) + 'px'
      boxSelect.style.height = Math.abs(_y - this.startY) + 'px'
      boxSelect.style.zIndex = 100
      this.findNodes(event)
      // 当框选超出范围时，结束框选操作,取消move和mouseup监听
      if (clientX < this.parentX || clientX > this.parentX + this.parentW || clientY < this.parentY || clientY > this.parentY + this.parentH) {
        document.onmousemove = null
        this.$refs[parentDom].onmouseup = null
        clearTimeout(this.mouseTime)

        this.boxStop(event)
      }
    },
    // 鼠标松开事件
    boxStop (event) {
      this.mouseOn = false
      this.findNodes(event)
      let boxSelect = this.$refs.boxSelect
      boxSelect.style.display = 'none'
      boxSelect.style.width = 0
      boxSelect.style.height = 0
    },
    // 获取框选下的元素
    findNodes (event) {
      let dayNodes = document.getElementsByClassName('file-item')
      let boxSelect = this.$refs.boxSelect
      let selectedEls = []
      // 获取框的参数
      let l = boxSelect.offsetLeft
      let t = boxSelect.offsetTop
      let w = boxSelect.offsetWidth
      let h = boxSelect.offsetHeight

      for (let i = 0; i < dayNodes.length; i++) {
        let dayNode = dayNodes[i]
        let sl = dayNode.offsetWidth + dayNode.offsetLeft
        let st = dayNode.offsetHeight + dayNode.offsetTop
        if (sl > l && st > t && dayNode.offsetLeft < l + w && dayNode.offsetTop < t + h) {
          selectedEls.push(this.files[i])
        }
      }
      if (event.ctrlKey || event.shiftKey) {
        this.actFile = this.actFile.concat(selectedEls)
      } else {
        this.actFile = selectedEls
      }
    },
    // 提示
    // 注意提示
    noticeMessage (message) {
      this.$q.dialog({
        title: `<p class="dialog-notice-title">
<i class="iconfont icontubiao-28" ></i> 提示</p>`,
        message: message,
        html: true,
        ok: {
          unelevated: true,
          label: '确认'
        }
      })
    },
    // 成功提示
    successMessage (message) {
      this.$q.dialog({
        title: `<p class="dialog-notice-title">
<i class="iconfont icontubiao-53"></i> ${message}</p>`,
        html: true,
        ok: {
          unelevated: true,
          label: '确认'
        }
      })
    },
    // 失败提示
    errMessage (message) {
      this.$q.dialog({
        title: `<p class="dialog-notice-title">
<i class="iconfont icontubiao-52" ></i> 提示</p>`,
        message: message,
        html: true,
        ok: {
          unelevated: true,
          label: '确认'
        }
      })
    },

    // 根据开始时间和结束时间，获取累计用时时间，返回hh:mm:ss
    getUseTime (startTime, endTime) {
      let useTime = (endTime.getTime() - startTime.getTime()) / 1000
      let seconds = parseInt(useTime % 60)
      seconds = seconds < 10 ? '0' + seconds : seconds
      let minutes = parseInt(useTime / 60 % 60)
      minutes = minutes < 10 ? '0' + minutes : minutes
      let hours = parseInt(useTime / 3600 % 60)
      hours = hours < 10 ? '0' + hours : hours
      return `${hours}:${minutes}:${seconds}`
    }
  },
  components: {
    Position,
    FileItem,
    FileItemTr,
    CodeEdit,
    CompareFile,
    VersionList,
    ProgressDialog,
    ProgressResultDialog,
    CodeMerge
  }
}
</script>

<style scoped lang="stylus">
.right-all{
  .file-head{
    padding 0 20px
    height 40px
    line-height 40px
    font-size 14px
    align-items center
    .icon{
      margin-left 10px
      font-size 18px
    }
  }
  .list-content{
    height calc(100% - 120px)
  }
  .list{
    padding 10px
    display flex
    flex-wrap wrap
  }
  .table-file {
    height 100%
  }
  .file-bottom{
    text-align right
    padding 0 20px
    height 39px
    line-height 39px
    border-top 1px solid #d3d3d3
    .text-underline{
      text-decoration underline
    }
  }
  // 框选样式
  .box-select{
    position absolute
    margin 0
    padding 0
    top 0
    left 0
    width 10px
    height 10px
    border 1px dashed $primary
    background-color $primary
    z-index 1000
    opacity 0.6
    display none
  }
}
</style>
<style lang="stylus">
.right-all  .table-file{
  .q-table__middle{
    height calc(100%)
  }
}
</style>
